/*
 *   A task scheduler that implements Monte-Carlo simulation
 *
 *   Copyright (C) 2011 Systasis Computer Systems, Inc.
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *   
 *   $Id: Duration.java 68 2011-08-01 22:22:17Z jchimene@gmail.com $
 */
package com.systasis.sked.client.presenter;

import java.util.Date;

public class Duration {
    public static class DurationBuilder implements Builder<Duration> {
	private final double[] duration;

	public DurationBuilder() {
	    duration = new double[SIZEOF];
	}

	public DurationBuilder best(double val) {
	    duration[BEST] = val;
	    return this;
	}

	@Override
	public Duration build() {
	    return new Duration(this);
	}

	public DurationBuilder duration(Duration duration) {
	    worst(duration.getWorst());
	    best(duration.getBest());
	    likely(duration.getLikely());
	    return this;
	}

	public long getTimestamp() {
	    final Date date = new Date();
	    return date.getTime();
	}

	public DurationBuilder likely(double val) {
	    duration[LIKELY] = val;
	    return this;
	}

	public DurationBuilder random(double val) {
	    duration[RANDOM] = val;
	    return this;
	}

	public DurationBuilder worst(double val) {
	    duration[WORST] = val;
	    return this;
	}
    }

    public interface Exporter {
	public interface asDouble {
	    void addBest(Double best);

	    void addEstimate(Double estimate);

	    void addLikely(Double likely);

	    void addRandom(Double random);

	    void addSkew(Double skew);

	    void addWorst(Double worst);
	}

	void addBest(String best);

	void addEstimate(String estimate);

	void addLikely(String likely);

	void addRandom(String random);

	void addSkew(String skew);

	void addWorst(String worst);
    }

    public interface Importer {
	Double provideBest();

	Double provideEstimate();

	Double provideLikely();

	Double provideRandom();

	Double provideSkew();

	Double provideWorst();
    }

    private static final int BEST = 0;

    private static final int ESTIMATE = 3;

    private static final int LIKELY = 1;

    private static final int RANDOM = 4;

    private static final int SIZEOF = 6;

    private static final int SKEW = 5;

    private static final int WORST = 2;

    private Double[] duration = new Double[SIZEOF];

    public Duration(Importer importer) {
	this.duration[LIKELY] = importer.provideLikely();
	this.duration[BEST] = importer.provideBest();
	this.duration[WORST] = importer.provideWorst();
	this.duration[ESTIMATE] = importer.provideEstimate();
	this.duration[RANDOM] = importer.provideRandom();
	this.duration[SKEW] = importer.provideSkew();
    }

    private Duration(DurationBuilder builder) throws IllegalStateException {
	if (0.0 == builder.duration[LIKELY]) {
	    duration[LIKELY] = 0.0;
	    duration[BEST] = 0.0;
	    duration[WORST] = 0.0;
	    duration[RANDOM] = 0.0;
	    duration[SKEW] = 0.0;
	    duration[ESTIMATE] = 0.0;
	    return;
	}

	if (builder.duration[LIKELY] <= builder.duration[BEST]) {
	    throw new IllegalStateException("likely <= best");
	}

	if ((0 < builder.duration[WORST]) && (builder.duration[LIKELY] >= builder.duration[WORST])) {
	    throw new IllegalStateException("likely >= worst");
	}

	if ((0 < builder.duration[WORST]) && (0 < builder.duration[BEST]) && (builder.duration[WORST] <= builder.duration[BEST])) {
	    throw new IllegalStateException("worst <= best");
	}
	duration[LIKELY] = builder.duration[LIKELY];
	duration[BEST] = (double) ((0 == builder.duration[BEST]) ? builder.duration[LIKELY] * 0.9 : builder.duration[BEST]);
	duration[WORST] = (double) ((0 == builder.duration[WORST]) ? builder.duration[LIKELY] * 1.1 : builder.duration[WORST]);
	duration[RANDOM] = (0.0 == builder.duration[RANDOM]) ? Math.random() : builder.duration[RANDOM];
	duration[SKEW] = (duration[LIKELY] - duration[BEST]) / (duration[WORST] - duration[BEST]);
	duration[ESTIMATE] = duration[SKEW] >= duration[RANDOM] ? estimateBest() : estimateWorst();
    }

    public void export(Exporter exporter) {
	exporter.addLikely(duration[LIKELY].toString().substring(0, Math.min(duration[LIKELY].toString().length(), 4)));

	exporter.addBest(duration[BEST].toString().substring(0, Math.min(duration[BEST].toString().length(), 4)));

	exporter.addWorst(duration[WORST].toString().substring(0, Math.min(duration[WORST].toString().length(), 4)));

	exporter.addRandom(duration[RANDOM].toString().substring(0, Math.min(duration[RANDOM].toString().length(), 5)));

	exporter.addSkew(duration[SKEW].toString().substring(0, Math.min(duration[SKEW].toString().length(), 4)));

	exporter.addEstimate(duration[ESTIMATE].toString().substring(0, Math.min(duration[ESTIMATE].toString().length(), 4)));
    }

    public void export(Exporter.asDouble exporter) {
	exporter.addLikely(duration[LIKELY]);

	exporter.addBest(duration[BEST]);

	exporter.addWorst(duration[WORST]);

	exporter.addEstimate(duration[ESTIMATE]);

	exporter.addRandom(duration[RANDOM]);

	exporter.addSkew(duration[SKEW]);
    }

    public Double getBest() {
	return duration[BEST];
    }

    public Double getEstimate() {
	return duration[ESTIMATE];
    }

    public Double getLikely() {
	return duration[LIKELY];
    }

    public Double getWorst() {
	return duration[WORST];
    }

    public void setBest(Double best) {
	duration[BEST] = best;
    }

    public void setDuration(Duration duration) {
	this.duration = duration.duration;
    }

    public void setEstimate(double estimate) {
	duration[ESTIMATE] = estimate;
    }

    public void setLikely(Double value) {
	duration[LIKELY] = value;
    }

    public void setWorst(Double worst) {
	duration[WORST] = worst;
    }

    private Double estimateBest() {
	return duration[BEST] + Math.sqrt(duration[RANDOM] * (duration[WORST] - duration[BEST]) * (duration[LIKELY] - duration[BEST]));
    }

    private Double estimateWorst() {
	return duration[WORST] - Math.sqrt((1 - duration[RANDOM]) * (duration[WORST] - duration[BEST]) * (duration[WORST] - duration[LIKELY]));
    }
}
